﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Xml.Linq;
using static LightCAD.Three.MathUtils;
using static LightCAD.Three.TypeUtils;
using LightCAD.Three.OpenGL;

namespace LightCAD.Three
{

    public class WebGLGeometries
    {
        public readonly JsObj<int, bool> geometries = new JsObj<int, bool>();
        public readonly JsObj<BufferGeometry, BufferAttribute> wireframeAttributes = new JsObj<BufferGeometry, BufferAttribute>();
        public readonly WebGLAttributes attributes;
        public readonly WebGLInfo info;
        private WebGLBindingStates bindingStates;
        public WebGLGeometries(WebGLAttributes attributes, WebGLInfo info, WebGLBindingStates bindingStates)
        {
            this.attributes = attributes;
            this.info = info;
            this.bindingStates = bindingStates;
            this.geometries = new JsObj<int, bool>();
            this.wireframeAttributes = new JsObj<BufferGeometry, BufferAttribute>();
        }
        public void onGeometryDispose(EventArgs e)
        {
            var geometry = (BufferGeometry)e.target;
            if (geometry.index != null)
            {

                attributes.remove(geometry.index);
            }

            foreach (var item in geometry.attributes)
            {
                var name = item.Key;
                attributes.remove(geometry.attributes[name]);
            }
            geometry.removeEventListener("dispose", onGeometryDispose);
            geometries.remove(geometry.id);
            var attribute = wireframeAttributes.get(geometry);
            if (attribute != null)
            {
                attributes.remove(attribute);
                wireframeAttributes.remove(geometry);
            }

            bindingStates.releaseStatesOfGeometry(geometry);

            if (geometry is InstancedBufferGeometry)
            {
                (geometry as InstancedBufferGeometry)._maxInstanceCount = 0; ;
                //delete geometry._maxInstanceCount;

            }

            //

            info.memory.geometries--;

        }
        public BufferGeometry get(Object3D _object, BufferGeometry geometry)
        {

            if (geometries[geometry.id]) return geometry;

            geometry.addEventListener("dispose", onGeometryDispose);

            geometries[geometry.id] = true;

            info.memory.geometries++;

            return geometry;

        }

        public void update(BufferGeometry geometry)
        {

            var geometryAttributes = geometry.attributes;

            // Updating index buffer in VAO now. See WebGLBindingStates.

            foreach (var item in geometryAttributes)
            {
                var name = item.Key;
                attributes.update(geometryAttributes[name], gl.ARRAY_BUFFER);

            }

            // morph targets

            var morphAttributes = geometry.morphAttributes;

            foreach (var item in morphAttributes)
            {
                var name = item.Key;
                var array = morphAttributes[name];

                for (int i = 0, l = array.length; i < l; i++)
                {
                    attributes.update(array[i], gl.ARRAY_BUFFER);
                }

            }

        }

        public void updateWireframeAttribute(BufferGeometry geometry)
        {
            var indices = new JsArr<int>();
            var geometryIndex = geometry.index;
            var geometryPosition = geometry.attributes.position;
            var version = 0;

            if (geometryIndex != null)
            {

                var array = geometryIndex.intArray;
                version = geometryIndex.version;

                for (int i = 0, l = array.Length; i < l; i += 3)
                {

                    var a = array[i + 0];
                    var b = array[i + 1];
                    var c = array[i + 2];

                    indices.push(a, b, b, c, c, a);

                }

            }
            else
            {

                var array = geometryPosition.array;
                version = geometryPosition.version;

                for (int i = 0, l = (array.Length / 3) - 1; i < l; i += 3)
                {

                    var a = i + 0;
                    var b = i + 1;
                    var c = i + 2;

                    indices.push(a, b, b, c, c, a);
                }
            }

            BufferAttribute attribute;
            if (arrayNeedsUint32(indices.ToArray()))
                attribute = new Uint32BufferAttribute(indices.ToArray(), 1);
            else
                attribute = new Uint16BufferAttribute(indices.Select(i => (UInt16)i).ToArray(), 1);
            attribute.version = version;

            // Updating index buffer in VAO now. See WebGLBindingStates

            //

            var previousAttribute = wireframeAttributes.get(geometry);

            if (previousAttribute != null) attributes.remove(previousAttribute);

            //

            wireframeAttributes.set(geometry, attribute);

        }

        public BufferAttribute getWireframeAttribute(BufferGeometry geometry)
        {

            var currentAttribute = wireframeAttributes.get(geometry);

            if (currentAttribute != null)
            {

                var geometryIndex = geometry.index;

                if (geometryIndex != null)
                {

                    // if the attribute is obsolete, create a new one

                    if (currentAttribute.version < geometryIndex.version)
                    {

                        updateWireframeAttribute(geometry);

                    }

                }

            }
            else
            {
                updateWireframeAttribute(geometry);
            }

            return wireframeAttributes.get(geometry);

        }
    }
}
